//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include <bsp.h>

#define INTEL_MAX_BLOCKS      128      // Maximum number of Flash blocks
#define INTEL_BYTES_SECTOR    512      // Number of bytes per sector
#define INTEL_WORDS_BLOCK     65536    // Number of words per block
#define INTEL_LOCK_TIMEOUT    1        // millisecond
#define INTEL_UNLOCK_TIMEOUT  700      // milliseconds

//****************************************************************************
//
// Waits until bit 7 becomes one, indicating that the
// embedded program (be it a chip erase, sector erase, or data store program)
// has completed executing.
//
//****************************************************************************
void
FlashWaitForProgram(unsigned long ulFlashBase)
{
    unsigned long * volatile puPtr = (unsigned long *)ulFlashBase;
    unsigned long temp, i = 0;

    //
    // Read from the FLASH memory until bit 7 is one.
    //
    while (1) {
        //
        // Read the status register from the FLASH memory.
        //
        temp = *puPtr;

        //
        // Mask off all bits except for bit 7.
        //
        temp &= 0x00800080;

        //
        // If bit 7 is one, then quit waiting.
        //
        if (temp == 0x00800080) {
            break;
        }
        *(ULONG*)0x08000010 = i++;
    }

    //
    // Clear the status register.
    //
    *puPtr = 0x00500050;
}

void debug(unsigned char c);

//****************************************************************************
//
// Erases a specific sector of the FLASH memory.
//
//****************************************************************************
#define PERSECTSIZE 0x40000 /* 256K */
extern "C" int FlashEraseSector(unsigned long ulFlashBase, long lSectorOffset);

int FlashEraseSectors(unsigned long FlashBase, long Offset, long EraseSize)
{

//    unsigned long * volatile puPtr = (unsigned long *)FlashBase;
    EraseSize = Offset + EraseSize;

    for (;Offset < EraseSize; Offset += PERSECTSIZE) {
        FlashEraseSector(FlashBase,Offset);
    }

    return 0;
}

/*
 *                                                                           *
 * FlashEraseSector erases a specific sector of the flash ROM.               *
 *                                                                           *
 */
#define Intel_SR_DeviceProtect      (1U << 1)  // 1 = Block Lock-bit detected
                                               // 0 = Unlock
#define Intel_SR_ProgramSuspend     (1U << 2)  // 1 = Program suspended
                                               // 0 = Program in progress/done
#define Intel_SR_ProgrammingVoltage (1U << 3)  // 1 = Vpp low voltage detected
                                               // 0 = Vpp voltage okay
#define Intel_SR_ProgramSetLockBit  (1U << 4)  // 1 = Error in Setting Lock-Bit
                                               // 0 = Successfully set Lock-Bit
#define Intel_SR_EraseClearLockBit  (1U << 5)  // 1 = Error in Block Erasure or
                                               //     Clear Lock-Bits
                                               // 0 = Successful Block Erase or
                                               //     Clear Lock-Bits
#define Intel_SR_EraseSuspend       (1U << 6)  // 1 = Block Erase Suspended
                                               // 0 = Block Erase in Progress/
                                               //     completed
#define Intel_SR_WriteStateMachine  (1U << 7)  // 1 = Ready, 0 = Busy

#define Intel_SR_Errors             (Intel_SR_EraseClearLockBit  | \
                                     Intel_SR_ProgramSetLockBit  | \
                                     Intel_SR_ProgrammingVoltage | \
                                     Intel_SR_DeviceProtect)

#define DoubleHalf(a) (((unsigned)(a) << 16) | (a))

static
Int32 getFlashGeometry(UInt32 ulFlashBase, UInt32 lSectorOffset);

extern "C" int FlashEraseSector(unsigned long ulFlashBase, long lSectorOffset)
{
    unsigned long * volatile pulPtr = (unsigned long *)ulFlashBase;
    UInt32 readStatus, off =lSectorOffset >> 2;

    getFlashGeometry(ulFlashBase, off);

    pulPtr[lSectorOffset >> 2] = 0x00600060;
    pulPtr[lSectorOffset >> 2] = 0x00d000d0;

    FlashWaitForProgram(ulFlashBase + lSectorOffset);

    pulPtr[lSectorOffset >> 2] = 0x00700070;

    readStatus = pulPtr[lSectorOffset >> 2];

    if (readStatus & Intel_SR_Errors) {
        pulPtr[lSectorOffset >> 2] = 0x00500050;
        pulPtr[lSectorOffset >> 2] = 0xffffffff;
        *(ULONG *)(0x08000010) = 0xA;
        while(1);
    }
    pulPtr[lSectorOffset >> 2] = 0xffffffff;
    pulPtr[lSectorOffset >> 2] = 0x00980098;

    UInt32 q, r, y;

    // Retrieve the string "QRY".
    q = pulPtr[(lSectorOffset >> 2) + 0x10];
    r = pulPtr[(lSectorOffset >> 2) + 0x11];
    y = pulPtr[(lSectorOffset >> 2) + 0x12];
    if ((q != 0x00510051) || (r != 0x00520052) || (y != 0x00590059)) // Set the Flash into the read array mode by writing the Read Array
    {
        // command code to any Flash address.
        pulPtr[lSectorOffset >> 2]= 0xffffffff;
        *(ULONG *)(0x08000010) = 0xB;
        while(1);
    }

    UInt32 blockStatus;
    blockStatus = pulPtr[(lSectorOffset >> 2)+2];
    pulPtr[lSectorOffset >> 2]= 0xffffffff;

    if (blockStatus & 1) {
        *(ULONG *)(0x08000010) = 0xC;
        while(1);
    }

    pulPtr[lSectorOffset >> 2]= 0xffffffff;
    pulPtr[lSectorOffset >> 2]= 0x00500050;

    // Write the erase command to the FLASH memory.
    //
    pulPtr[lSectorOffset >> 2] = 0x00200020;
    pulPtr[lSectorOffset >> 2] = 0x00D000D0;
    FlashWaitForProgram(ulFlashBase + lSectorOffset);

    pulPtr[lSectorOffset >> 2]= 0xffffffff;

    //verify whether erase ok
    if (pulPtr[lSectorOffset >> 2] != 0xffffffff) {
        *(ULONG *)0x08000010 = 0x46;
        while(1);
    }

    return 0;
}

//****************************************************************************
//
// Read a block of data into DRAM from Flash memory
//
//****************************************************************************
extern "C" int FlashReadBuffer(unsigned long FlashBase, long Offset, unsigned char *Buffer, long BufferSize)
{
    unsigned long * volatile puPtr = (unsigned long *)FlashBase;
    long i;

    puPtr[Offset>>2] = 0xffffffff;
    for (i = 0; i < BufferSize; i += sizeof(unsigned long)) {
        *((unsigned long *)(Buffer + i)) = puPtr[(Offset + i) >> 2];
    }
    return 0;
}

//****************************************************************************
//
// Writes a block of data into the FLASH memory.
//
//****************************************************************************
void *          __cdecl malloc(size_t);

extern "C" int FlashWriteBuffer(unsigned long ulFlashBase, long par, unsigned char *Buffer, long BufferSize)
{

    unsigned long * volatile pulPtr = (unsigned long *)ulFlashBase;
    unsigned int * memoryptr = (unsigned int *)Buffer;

    UInt32 lSectorOffset = (par>>18) * 0x40000;

    for (Int32 i = 0; i < BufferSize; i += 0x80) {
        UCHAR temp[0x80];
        FlashReadBuffer(ulFlashBase,par + i,temp,0x80);

        pulPtr[lSectorOffset >> 2] = 0x00600060;
        pulPtr[lSectorOffset >> 2] = 0x00d000d0;

        FlashWaitForProgram(ulFlashBase + lSectorOffset);

        pulPtr[lSectorOffset >> 2] = 0x00700070;
        int readStatus;
        readStatus = pulPtr[lSectorOffset >> 2];

        if (readStatus & Intel_SR_Errors) {
            pulPtr[lSectorOffset >> 2] = 0x00500050;
            pulPtr[lSectorOffset >> 2] = 0xffffffff;
            *(ULONG *)(0x08000010) = 0xAA;
            while(1);
        }
        pulPtr[lSectorOffset >> 2] = 0xffffffff;
        pulPtr[lSectorOffset >> 2] = 0x00980098;

        UInt32 q,r,y;
        // Retrieve the string "QRY".
        q = pulPtr[(lSectorOffset >> 2) + 0x10];
        r = pulPtr[(lSectorOffset >> 2) + 0x11];
        y = pulPtr[(lSectorOffset >> 2) + 0x12];
        if ((q != 0x00510051) || (r != 0x00520052) || (y != 0x00590059)) // Set the Flash into the read array mode by writing the Read Array
        {
            // command code to any Flash address.
            pulPtr[lSectorOffset >> 2]= 0xffffffff;
            *(ULONG *)(0x08000010) = 0xBB;
            while(1);
        }

        int blockStatus;
        blockStatus = pulPtr[(lSectorOffset >> 2)+2];
        pulPtr[lSectorOffset >> 2]= 0xffffffff;

        if (blockStatus & 1) {
            *(ULONG *)(0x08000010) = 0xCC;
            while(1);
        }

        pulPtr[lSectorOffset >> 2]= 0xffffffff;
        pulPtr[lSectorOffset >> 2]= 0x00500050;

        pulPtr[lSectorOffset >> 2]= 0x00e800e8;

        while ((pulPtr[lSectorOffset >> 2] & 0x00800080) !=
            0x00800080) {
            *(ULONG *)0x08000010 = 0x78;
            // Enter the write to buffer command and check for buffer available
            pulPtr[lSectorOffset >> 2] = 0x00e800e8;
        }

        Int32 writeSize = 0x80;//2 * (1 << (pulPtr[(lSectorOffset >> 2) + 0x2a] & 0xffff));
        pulPtr[lSectorOffset >> 2] = DoubleHalf((writeSize >> 2) - 1);

        int realData = (BufferSize - i) > 0x80? 0x80:(BufferSize - i);

        if ((realData == 0x80))
        // Now copy the data from the sector cache to the device.
            for (Int32 j = 0; j < writeSize >> 2; j++) {
                pulPtr[(par >> 2)+ (i>>2)+j] = *memoryptr++;
            }
        else {
            Int32 j;
            for (j = 0; j < (realData >> 2); j++) {
                pulPtr[(par >> 2)+ (i>>2)+j] = *memoryptr++;
            }

            ULONG data = *(ULONG*)(temp + (realData>>2));

            if ((realData&0x3) == 2) {
                pulPtr[(par >> 2)+ (i>>2) + j]
                        = (*(USHORT *)(memoryptr))| (data&0xffff0000);
            }
            else {
                pulPtr[(par >> 2)+ (i>>2) + j] = data;
            }

            for (j = (realData >> 2) + 1; j < (writeSize >> 2); j++) {
                pulPtr[(par >> 2)+ (i >> 2) + j] = *(ULONG*)(temp + j);
            }
        }

        pulPtr[lSectorOffset >> 2] = DoubleHalf(0xd0);

        //waitReadyStatus
        FlashWaitForProgram(ulFlashBase + lSectorOffset);

        //checkStatusRegister
        pulPtr[lSectorOffset >> 2] = 0x00700070;

        readStatus = pulPtr[lSectorOffset >> 2];

        if (readStatus & Intel_SR_Errors) {
            pulPtr[lSectorOffset >> 2] = 0x00500050;
            pulPtr[lSectorOffset >> 2] = 0xffffffff;
            *(ULONG *)(0x08000010) = readStatus;
            while(1);
        }

        pulPtr[lSectorOffset >> 2] = 0xffffffff;
    }
    return 0;
}

int ProgramFlash(unsigned long FlashBase, long Offset, unsigned char *Buffer, long BufferSize)
{
    int ret;
    ret = FlashEraseSectors(FlashBase, Offset, BufferSize);
    if (ret<0) return ret;
    ret = FlashWriteBuffer(FlashBase, Offset, Buffer, RoundUp(BufferSize, 4));
    return ret;
}

Int32 FlashHWSetup()
{
    return 0;
}

typedef enum Intel_Registers_E {
    Intel_MfgCode             = 0,     // The manufacturing code
    Intel_DeviceCode          = 1,     // The device code
    Intel_BlockStatus         = 2,     // Block Status Register
    Intel_QueryQ              = 0x10,  // "Q"
    Intel_QueryR              = 0x11,  // "R"
    Intel_QueryY              = 0x12,  // "Y"
    Intel_TypicalWriteTimeOut = 0x20,  // Block write timeout in microseconds
    Intel_TypicalEraseTimeOut = 0x21,  // Block erase timeout in milliseconds
    Intel_MaxWriteTimeOut     = 0x24,  // Maximum write timeout multiplied by
                                       // value of Intel_TypicalWriteTimeout
    Intel_MaxEraseTimeOut     = 0x25,  // Maximum erase timeout multiplied by
                                       // value of Intel_TypicalEraseTimeout
    Intel_DeviceSize          = 0x27,  // Device size (2n) in bytes
    Intel_WriteBlkSize        = 0x2a,  // Maximum size of the write buffer
    Intel_BlksRegion1         = 0x2d,  // Number of Erase Blocks in Region 1
    Intel_BlkSize1L           = 0x2f,  // Size of blocks in Region 1 (lower part)
    Intel_BlkSize1U           = 0x30,  // Size of blocks in Region 1 (upper part)
    Intel_BlksRegion2         = 0x31,  // Number of Erase Blocks in Region 2
    Intel_BlkSize2L           = 0x33,  // Size of blocks in Region 2 (lower part)
    Intel_BlkSize2U           = 0x34   // Size of blocks in Region 2 (upper part)
} Intel_Registers_T;

static
Int32 verifyFlashDevice(UInt32 ulFlashBase, UInt32 lSectorOffset);

static
Int32 getFlashGeometry(UInt32 ulFlashBase, UInt32 lSectorOffset)
{
    unsigned long * volatile pulPtr = (unsigned long *)ulFlashBase;

    UInt32 q, r, y;
    UInt32 tmp;

    verifyFlashDevice(ulFlashBase, lSectorOffset);

    // Send the Read Query command.
    pulPtr[lSectorOffset] = DoubleHalf(0x0098);

    // Retrieve the string "QRY".
    q = pulPtr[lSectorOffset + 0x10];
    r = pulPtr[lSectorOffset + 0x11];
    y = pulPtr[lSectorOffset + 0x12];
    if ((q != DoubleHalf('Q')) || (r != DoubleHalf('R')) || (y != DoubleHalf('Y'))) {
        // Set the Flash into the read array mode by writing the Read Array
        // command code to any Flash address.
        pulPtr[lSectorOffset] = DoubleHalf(0xffff);
        *(ULONG *)(0x08000010) = q;

        return (1);
    }

    /*
    ***************************************************************************
    * Now retrieve the Flash device parameters and geometry needed to manage
    * and program the Flash.
    ***************************************************************************
    */

    // Get the maximum write time. This is expressed as a multiplier to be
    // applied to the typical write time (expressed in uS).
    tmp = 1 << (pulPtr[lSectorOffset + Intel_TypicalWriteTimeOut] & 0xffff);

//    UInt32 writeTime =
//        (1 << (pulPtr[lSectorOffset + Intel_MaxWriteTimeOut] & 0xffff)) * tmp;

    // Get the maximum erase time. This is expressed as a multiplier to be
    // applied to the typical erase time (expressed in mS).
    tmp = 1 << (pulPtr[lSectorOffset + Intel_TypicalEraseTimeOut] & 0xffff);
//    UInt32 eraseTime =
//        (1 << (pulPtr[lSectorOffset + Intel_MaxEraseTimeOut] & 0xffff)) * tmp;

    // Get the device geometry. Our board design uses two WORD devices
    // together, so we need to multply the sizes by 2.
//    UInt32 maxBlock = 2 * (1 << (pulPtr[lSectorOffset + Intel_DeviceSize] & 0xffff)) >> 9;
//    UInt32 writeSize = 2 * (1 << (pulPtr[lSectorOffset + Intel_WriteBlkSize] & 0xffff));

    // Get the number of erase blocks in Region 1
//    UInt32 blocksRegion1 =  1 + (pulPtr[lSectorOffset + Intel_BlksRegion1] & 0xffff);

    // Calculate erase size for Region 1 of the flash
    // Upper byte of # of 512 buffers
    tmp = (pulPtr[lSectorOffset + Intel_BlkSize1U] & 0xffff) << 8;
    // Lower byte of # of 512 buffers
//    UInt32 blockSizeReg1 = tmp + (pulPtr[lSectorOffset + Intel_BlkSize1L] & 0xffff);
    // Calculate Region 1 erase size
//    UInt32 eraseSize = blockSizeReg1 * 512;

#if 1
    // If this device has a region 2 block size, get the eraseSize value from region 2
        // Get the number of erase blocks in Region 2
//        UInt32 blocksRegion2 =  1 + (pulPtr[lSectorOffset + Intel_BlksRegion2] & 0xffff);
        // Calculate erase size for Region 1 of the flash
        // Upper byte of # of 512 buffers
    tmp = (pulPtr[lSectorOffset + Intel_BlkSize2U] & 0xffff) << 8;
        // Lower byte of # of 512 buffers
//        UInt32 blockSizeReg2 = tmp + (pulPtr[lSectorOffset + Intel_BlkSize2L] & 0xffff);
        // Calculate Region 2 erase size
//        UInt32 eraseSize2 = UInt32 (blockSizeReg2 * INTEL_BYTES_SECTOR);

    // Region 1 size calculation
//    UInt32 blocksPerSector = UInt32 (eraseSize>>9);
    // Region 2 size calculation
//    UInt32 blocksPerSector2 = UInt32(eraseSize2>>9);
#endif

    // Set the Flash into the read array mode by writing the Read Array command
    // code to any Flash address.
    pulPtr[lSectorOffset] = DoubleHalf(0xffff);

    return (0);
}

typedef enum Intel_Command_E {
    Intel_SetLock      = 0x01,         // Set lock confirm value
    Intel_AltByteWrite = 0x10,         // Alternate Byte Write Setup/Write
    Intel_BlockErase   = 0x20,         // Block Erase
    Intel_ByteWrite    = 0x40,         // Word/Byte Program - Same as command
                                       // Intel_AltByteWrite for all by the
                                       // 28F008SA device
    Intel_ClearStatus  = 0x50,         // Clear Status Register
    Intel_BlockLock    = 0x60,         // Set/Clear Block Lock-bits
    Intel_ReadStatus   = 0x70,         // Read Status Register
    Intel_Ready        = 0x80,         // Determine the device is ready
    Intel_ReadIdCodes  = 0x90,         // Read Identifier Codes
    Intel_ReadQuery    = 0x98,         // Read Query, Not supported on 28F998SA
    Intel_Confirm      = 0xD0,         // Block Erase, Program Resume, Not
                                       // supported on the 28F998SA
    Intel_WriteBuffer  = 0xE8,         // Write to Buffer, Not supported on
                                       // the 28F998SA
    Intel_ReadArray    = 0xffff        // Read Arrary
} Intel_Command_T;

typedef enum Intel_DeviceCodes_E {
  Intel_28F320J3A  = 0x0016,
  Intel_28F640J3A  = 0x0017,
  Intel_28F128J3A  = 0x0018,
  Intel_28F008SA   = 0x00A2,
  Intel_28F160S3   = 0x00D0,
  Intel_28F320S3   = 0x00D4,
  Intel_28F640K3   = 0x8801,
  Intel_28F128K3   = 0x8802,
  Intel_28F256K3   = 0x8803,
  Intel_28F640K18  = 0x8805,
  Intel_28F128K18  = 0x8806,
  Intel_28F256K18  = 0x8807,
  Intel_28F640L18T = 0x880B,
  Intel_28F128L18T = 0x880C,
  Intel_28F256L18T = 0x880D,
  Intel_28F640L18B = 0x880E,
  Intel_28F128L18B = 0x880F,
  Intel_28F256L18B = 0x8810
} Intel_DeviceCodes_T;

typedef enum Intel_MfgCodes_E {
  Intel_StrataFlash = 0x89,
#define Intel_Flash Intel_StrataFlash
  Intel_FlashFile   = 0xB0
} Intel_MfgCodes_T;

static
Int32 verifyFlashDevice(UInt32 ulFlashBase, UInt32 lSectorOffset)
{
    unsigned long * volatile pulPtr = (unsigned long *)ulFlashBase;

    UInt32 mfgCode, devCode;

    // Set the Flash into the read array mode by writing the Read Array command
    // code to any Flash address.
    pulPtr[lSectorOffset] = DoubleHalf(0xffff);

    // Now check the Flash device to determine whether its a StrataFlash or
    // FlashFile device. These are the only Flash devices currently supported.
    // Write the command to the Flash device to read the manufacture
    // and device codes.
    pulPtr[lSectorOffset] = DoubleHalf(Intel_ReadIdCodes);
    mfgCode = pulPtr[lSectorOffset + Intel_MfgCode];
    devCode = pulPtr[lSectorOffset + Intel_DeviceCode];

    // Set the Flash into the read array mode by writing the Read Array command
    // code to any Flash address.
    pulPtr[lSectorOffset] = DoubleHalf(Intel_ReadArray);

    // Check for the Intel StrataFlash.
    if (mfgCode == DoubleHalf(Intel_StrataFlash)) {

        if (devCode == DoubleHalf(Intel_28F128J3A)) {
//            UInt32 flashType = Intel_28F128J3A;
        }
        else if (devCode == DoubleHalf(Intel_28F128K3)) {
//            UInt32 flashType = Intel_28F128K3;
        }
        else if (devCode == DoubleHalf(Intel_28F128K18)) {
//            UInt32 flashType = Intel_28F128K18;
        }
        else if (devCode == DoubleHalf(Intel_28F640L18T)) {
//            UInt32 flashType = Intel_28F640L18T;
        }
        else if (devCode == DoubleHalf(Intel_28F128L18T)) {
//            UInt32 flashType = Intel_28F128L18T;
        }
        else if (devCode == DoubleHalf(Intel_28F256L18T)) {
//            UInt32 flashType = Intel_28F256L18T;
        }
        else if (devCode == DoubleHalf(Intel_28F640L18B)) {
//            UInt32 flashType = Intel_28F640L18B;
        }
        else if (devCode == DoubleHalf(Intel_28F128L18B)) {
//            UInt32 flashType = Intel_28F128L18B;
        }
        else if (devCode == DoubleHalf(Intel_28F256L18B)) {
//            UInt32 flashType = Intel_28F256L18B;
        }
        else {

            return (0);
        }
    }
    // Check for the Intel FlashFile
    else if (mfgCode == DoubleHalf(Intel_FlashFile)) {
        if (devCode == DoubleHalf(Intel_28F160S3)) {
//            UInt32 flashType = Intel_28F160S3;
        }
        else {

            return (0);
        }
    }
    // Unsupported Flash device found.
    else {

        return (0);
    }

    // Set the Flash into the read array mode by writing the Read Array command
    // code to any Flash address.
    pulPtr[lSectorOffset] = DoubleHalf(Intel_ReadArray);

    return (0);
}
